package top.hmtools.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.Selector;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;

import top.hmtools.base.CharsetTools;
import top.hmtools.base.HexTools;
import top.hmtools.system.SystemInfoTools;



/**
 * 基于 IO（bio） 的操作流工具类
 * @author HyboJ
 *
 */
public class IoTools {

	/** 默认缓存大小 */
	public static final int DEFAULT_BUFFER_SIZE = 1024;
	/** 默认缓存大小 */
	public static final int DEFAULT_LARGE_BUFFER_SIZE = 4096;
	/** 数据流末尾 */
	public static final int EOF = -1;
	/**
     * 用于 skip() 方法的缺省缓存大小.
     */
    private static final int SKIP_BUFFER_SIZE = 2048;
    
    private static byte[] SKIP_BYTE_BUFFER;
    
    //##########################	数据流复制----开始	############################################
    

	/**
	 * 数据流复制
	 * @param input
	 * @param output
	 * @return
	 * @throws IOException
	 */
	public static long copy(final InputStream input, final OutputStream output)
			throws IOException {
		byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
		return copy(input, output, buffer, null);
	}

	/**
	 * 数据流复制
	 * @param input
	 * @param output
	 * @param buffer
	 * @return
	 * @throws IOException
	 */
	public static long copy(final InputStream input, final OutputStream output,final byte[] buffer)
			throws IOException {
		return copy(input, output, buffer, null);
	}

	/**
	 * 数据流复制
	 * @param input
	 * @param output
	 * @param streamProgress
	 * @return
	 * @throws IOException
	 */
	public static long copy(final InputStream input, final OutputStream output,IStreamProgress streamProgress)
			throws IOException {
		byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
		return copy(input, output, buffer, streamProgress);
	}
	
	/**
	 * 数据流复制
	 * @param input
	 * @param output
	 * @param buffer
	 * @param streamProgress
	 * @return
	 * @throws IOException
	 */
	public static long copy(final InputStream input, final OutputStream output,final byte[] buffer,IStreamProgress streamProgress)
            throws IOException {
		return copyLarge(input, output, buffer, streamProgress);
	}

	/**
	 * 支持复制容量超过2GB的数据流
	 * @param input
	 * @param output
	 * @return
	 * @throws IOException
	 */
	public static long copyLarge(final InputStream input, final OutputStream output)
			throws IOException {
		return copyLarge(input, output, null, null);
	}
	
	/**
	 * 支持复制容量超过2GB的数据流
	 * @param input
	 * @param output
	 * @param buffer
	 * @return
	 * @throws IOException
	 */
	public static long copyLarge(final InputStream input, final OutputStream output, final byte[] buffer)
            throws IOException {
		return copyLarge(input, output, buffer, null);
	}

	/**
	 * 支持复制容量超过2GB的数据流
	 * @param input
	 * @param output
	 * @param streamProgress
	 * @return
	 * @throws IOException
	 */
	public static long copyLarge(final InputStream input, final OutputStream output,IStreamProgress streamProgress)
			throws IOException {
		return copyLarge(input, output, null, streamProgress);
	}
	
	/**
	 *  支持复制容量超过2GB的数据流
	 * @param input
	 * @param output
	 * @param buffer	缓存
	 * @param streamProgress	数据读取进度条
	 * @return
	 * @throws IOException
	 */
	public static long copyLarge(final InputStream input, final OutputStream output, byte[] buffer,IStreamProgress streamProgress)
            throws IOException {
        long count = 0;//读取数据流的总数
        int currLength;//当次读取了的数据流长度
        
        if(null == buffer){
        	buffer = new byte[DEFAULT_LARGE_BUFFER_SIZE];
        }
        
        //记录数据读取进度条接口，开始操作
        if (null != streamProgress) {
			streamProgress.start();
		}
        
        while (EOF != (currLength = input.read(buffer))) {
            output.write(buffer, 0, currLength);
            count += currLength;
            
            //当前数据流已读取进度（数据长度）
            if (null != streamProgress) {
				streamProgress.progress(count);
			}
        }
        
        //当前数据流读取完毕
        if (null != streamProgress) {
			streamProgress.finish();
		}
        return count;
    }

	/**
	 * 从指定位置复制数据流，使用缺省的缓存大小
	 * @param input
	 * @param output
	 * @param inputOffset
	 * @param streamProgress
	 * @return
	 * @throws IOException
	 */
	public static long copyLarge(final InputStream input,final OutputStream output,  long inputOffset, IStreamProgress streamProgress)throws IOException {
		return copyLarge(input, output, inputOffset, -1,new byte[DEFAULT_LARGE_BUFFER_SIZE],streamProgress);
	}
	
	/**
	 * 从指定位置复制指定长度的数据流，使用缺省的缓存大小
	 * @param input
	 * @param output
	 * @param inputOffset
	 * @param length
	 * @param streamProgress
	 * @return
	 * @throws IOException
	 */
	public static long copyLarge(final InputStream input,final OutputStream output, final long inputOffset, final long length,IStreamProgress streamProgress)throws IOException {
		return copyLarge(input, output, inputOffset, length,	new byte[DEFAULT_LARGE_BUFFER_SIZE],streamProgress);
	}
	
	/**
	 * 从指定位置复制指定长度的数据流
	 * @param input	输入流
	 * @param output	输出流
	 * @param inputOffset	输入流欲读取的指定位置（起始点）
	 * @param length	欲读取流的长度
	 * @param buffer	缓存
	 * @param streamProgress	读取进度
	 * @return
	 * @throws IOException
	 */
	public static long copyLarge(final InputStream input,	final OutputStream output, final long inputOffset,final long length, byte[] buffer,IStreamProgress streamProgress) throws IOException {
		//检测欲读取流的起始位置是否大于0
		if (inputOffset > 0) {
			//丢弃欲读取流起始位置之前的数据流片段
			skipFully(input, inputOffset);
		}
		
		//检测要读取流的总长度
		if (length == 0) {
			return 0;
		}
		
		if(null == buffer){
			buffer = new byte[DEFAULT_LARGE_BUFFER_SIZE];
		}
		
		//缓存区的长度
		final int bufferLength = buffer.length;
		//将要单次读取流的长度
		int bytesToRead = bufferLength;
		if (length > 0 && length < bufferLength) {
			bytesToRead = (int) length;
		}
		int read;
		long totalRead = 0;
		
		//记录数据读取进度条接口，开始操作
        if (null != streamProgress) {
			streamProgress.start();
		}
        
		while (bytesToRead > 0	&& EOF != (read = input.read(buffer, 0, bytesToRead))) {
			output.write(buffer, 0, read);
			totalRead += read;
			if (length > 0) { 
				// only adjust length if not reading to the end Note the cast must work because buffer.length is an integer
				bytesToRead = (int) Math.min(length - totalRead, bufferLength);
			}
			
			//当前数据流已读取进度（数据长度）
            if (null != streamProgress) {
				streamProgress.progress(totalRead);
			}
		}
		
		//当前数据流读取完毕
        if (null != streamProgress) {
			streamProgress.finish();
		}
		return totalRead;
	}
	
	//##########################	数据流复制----结束	############################################

	
	//##########################	数据流读取----开始	############################################

	/**
	 * 读取数据流
	 * @param input
	 * @param offset
	 * @param length
	 * @param streamProgress
	 * @return
	 * @throws IOException
	 */
	public static byte[] readToByte(final InputStream input,final int offset,final int length,IStreamProgress streamProgress) throws IOException{
		if(length==0){
			return new byte[0];
		}
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		copyLarge(input, baos, offset, length, null, streamProgress);
		byte[] result = baos.toByteArray();
		return result;
	}
	
	/**
	 * 读取数据流到字符串，以utf-8字符编码
	 * @param input
	 * @param offset
	 * @param length
	 * @param streamProgress
	 * @return
	 * @throws IOException
	 */
	public static String readToStringUTF8(final InputStream input,final int offset,final int length,IStreamProgress streamProgress) throws IOException{
		byte[] resultBytes = readToByte(input, offset, length, streamProgress);
		String result = new String(resultBytes, CharsetTools.CHARSET_UTF_8);
		return result;
	}

	/**
	 * 读取数据流到字符串，以utf-8字符编码
	 * @param input
	 * @return
	 * @throws IOException
	 */
	public static String readToStringUTF8(final InputStream input) throws IOException{
		byte[] resultBytes = readToByte(input, 0, -1, null);
		String result = new String(resultBytes, CharsetTools.CHARSET_UTF_8);
		return result;
	}

	/**
	 * 读取数据流到字符串集合（按行分隔）,UTF-8字符编码
	 * @param inputStream
	 * @return
	 * @throws IOException
	 */
	public static List<String> readLineToStringListUTF8(InputStream inputStream) throws IOException {
		return readLineToStringList(inputStream, CharsetTools.CHARSET_UTF_8);
	}
	
	/**
	 *  读取数据流到字符串集合（按行分隔），并制定字符编码。
	 * <br>缺省使用当前操作系统缺省字符编码（windows 时 GBK，UNIX 是 utf-8）
	 * @param inputStream
	 * @param charset
	 * @return
	 * @throws IOException
	 */
	public static List<String> readLineToStringList(final InputStream inputStream,Charset charset) throws IOException {
		String string = readToString(inputStream, charset);
		String[] linesStr = string.split(SystemInfoTools.getOSLineSeparator());
		List<String> result = Arrays.asList(linesStr);
		return result;
	}

	/**
	 * 读取数据流到字符串，并制定字符编码。
	 * <br>缺省使用当前操作系统缺省字符编码（windows 时 GBK，UNIX 是 utf-8）
	 * @param input
	 * @param charset
	 * @return
	 * @throws IOException
	 */
	public static String readToString(final InputStream input,Charset charset) throws IOException{
		byte[] resultBytes = readToByte(input, 0, -1, null);
		String result = new String(resultBytes, charset==null?CharsetTools.defaultCharset():charset);
		return result;
	}
	
	/**
	 * 读取数据流，并以16进制表示
	 * @param inputStream
	 * @param length	读取的长度
	 * @param isLowerCase	是否英文小写字母
	 * @return
	 * @throws IOException
	 */
	public static String readToStringHex(InputStream inputStream,int length,boolean isLowerCase) throws IOException{
		byte[] bytes = readToByte(inputStream, 0, length, null);
		String result = HexTools.encodeHexStr(bytes, isLowerCase);
		return result;
	}
	
	/**
	 * 读取数据流，并以16进制表示
	 * @param inputStream
	 * @param length
	 * @param isLowerCase
	 * @return
	 * @throws IOException
	 */
	public static char[] readToCharsHex(InputStream inputStream,int length,boolean isLowerCase) throws IOException{
		byte[] bytes = readToByte(inputStream, 0, length, null);
		char[] result = HexTools.encodeHex(bytes, isLowerCase);
		return result;
	}
	
	/**
	 * 读取数据流的前56字节，并以16进制表示（英文字母大写）
	 * @param inputStream
	 * @return
	 * @throws IOException
	 */
	public static String readToHex56Upper(InputStream inputStream) throws IOException{
		return readToStringHex(inputStream, 56, false);
	}
	
	
	//##########################	数据流读取----结束	############################################

	
	//##########################	数据流写入----开始	############################################

	/**
	 * 将byte数组写入到输出流
	 * @param data
	 * @param output
	 * @throws IOException
	 */
	public static void write(final byte[] data, final OutputStream output)throws IOException {
		if (data != null) {
            output.write(data);
        }
    }
	
	/**
	 * 将byte数组按照指定字符编码写入输出流
	 * @param data
	 * @param output
	 * @param encoding
	 * @throws IOException
	 */
	public static void write(final byte[] data, final Writer output, final Charset encoding) throws IOException {
        if (data != null) {
            output.write(new String(data, CharsetTools.toCharset(encoding)));
        }
    }
	
	/**
	 * 将字符串按照指定字符编码写入输出流
	 * @param data
	 * @param output
	 * @param encoding
	 * @throws IOException
	 */
	public static void write(final String data,final OutputStream output, final Charset encoding) throws IOException{
		write(data.getBytes(encoding),output );
	}
	
	/**
	 * 带有缓冲区的将byte数组写入到输出流
	 * @param data
	 * @param output
	 * @throws IOException
	 */
	public static void writeChunked(final byte[] data, final OutputStream output)throws IOException {
        if (data != null) {
            int bytes = data.length;
            int offset = 0;
            while (bytes > 0) {
                int chunk = Math.min(bytes, DEFAULT_BUFFER_SIZE);
                output.write(data, offset, chunk);
                bytes -= chunk;
                offset += chunk;
            }
        }
    }
	
	
	//##########################	数据流写入----结束	############################################

	
	//##########################	关闭数据流----开始	############################################
	
	/**
	 * 关闭输入流
	 * @param input
	 */
	public static void closeQuietly(final Reader input) {
        closeQuietly((Closeable) input);
    }
	
	/**
	 * 关闭输出流
	 * @param output
	 */
	public static void closeQuietly(final Writer output) {
        closeQuietly((Closeable) output);
    }
	
	/**
	 * 关闭输入流
	 * @param input
	 */
	public static void closeQuietly(final InputStream input) {
        closeQuietly((Closeable) input);
    }
	
	/**
	 * 关闭输出流
	 * @param output
	 */
	public static void closeQuietly(final OutputStream output) {
		closeQuietly((Closeable) output);
	}
	
	/**
	 * 关闭流
	 * @param closeables
	 */
	public static void closeQuietly(final Closeable... closeables) {
        if (closeables == null) {
            return;
        }
        for (final Closeable closeable : closeables) {
            closeQuietly(closeable);
        }
    }
	
	/**
	 * 关闭socket端口
	 * @param sock
	 */
	public static void closeQuietly(final Socket sock) {
        if (sock != null) {
            try {
                sock.close();
            } catch (final IOException ioe) {
                // ignored
            }
        }
    }
	
	/**
	 * 关闭数据流
	 * @param closeable
	 */
	public static void closeQuietly(final Closeable closeable) {
        try {
            if (closeable != null) {
                closeable.close();
            }
        } catch (final IOException ioe) {
            // ignore
        }
    }
	
	/**
	 * 关闭选择器
	 * @param selector
	 */
	public static void closeQuietly(final Selector selector) {
        if (selector != null) {
            try {
                selector.close();
            } catch (final IOException ioe) {
                // ignored
            }
        }
    }
	
	/**
	 * 关闭serversocket端口
	 * @param sock
	 */
	public static void closeQuietly(final ServerSocket sock) {
        if (sock != null) {
            try {
                sock.close();
            } catch (final IOException ioe) {
                // ignored
            }
        }
    }
	
	
	//##########################	关闭数据流----结束	############################################
	
	/**
	 * 将字符串转化为输入流
	 * @param input
	 * @param encoding
	 * @return
	 */
	public static InputStream toInputStream(final String input, final Charset encoding) {
        return new ByteArrayInputStream(input.getBytes(CharsetTools.toCharset(encoding)));
    }
	
	/**
	 * 将字符串转化为输入流
	 * @param input
	 * @param encoding
	 * @return
	 * @throws IOException
	 */
	public static InputStream toInputStream(final String input, final String encoding) throws IOException {
        final byte[] bytes = input.getBytes(CharsetTools.toCharset(encoding));
        return new ByteArrayInputStream(bytes);
    }
	
	/**
	 * 跳过（忽略）输入流的指定长度
	 * @param input
	 * @param toSkip
	 * @return
	 * @throws IOException
	 */
	public static long skip(final InputStream input, final long toSkip) throws IOException {
        if (toSkip < 0) {
            throw new IllegalArgumentException("不能小于0:" + toSkip);
        }
        /*
         * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data
         * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer
         * size were variable, we would need to synch. to ensure some other thread did not create a smaller one)
         */
        if (SKIP_BYTE_BUFFER == null) {
            SKIP_BYTE_BUFFER = new byte[SKIP_BUFFER_SIZE];
        }
        long remain = toSkip;
        while (remain > 0) {
            // See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than delegating to skip()
            final long n = input.read(SKIP_BYTE_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE));
            if (n < 0) { // EOF
                break;
            }
            remain -= n;
        }
        return toSkip - remain;
    }
	
	/**
	 * 跳过（忽略）输入流的指定长度
	 * @param input
	 * @param toSkip
	 * @throws IOException
	 */
	public static void skipFully(final InputStream input, final long toSkip) throws IOException {
        if (toSkip < 0) {
            throw new IllegalArgumentException("不能小于0:"  + toSkip);
        }
        final long skipped = skip(input, toSkip);
        if (skipped != toSkip) {
            throw new EOFException("Bytes to skip: " + toSkip + " actual: " + skipped);
        }
    }

}
